using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Threading;
using System.Diagnostics;
using System.IO;

namespace cz.zcu.PreSti.XNADirectX
{
    public class XNADirectXPresentation : Presentation, IGraphicsDeviceService
    {

        #region Fields
        // Keep track of how many controls are sharing the singletonInstance.
        static int referenceCount;
       
        // Audio objects
        private AudioEngine engine;
        private SoundBank soundBank;
        private WaveBank waveBank;
        private Cue soundCue;

        // Graphic objects
        private object monitor = 1;
        private GraphicsDevice display;
        private SpriteBatch sprite;
        private Control windowControl;
        private PresentationParameters presentParams;

        // Analysis objects
        private bool testMode = false;
        private Stopwatch stopwatch = new Stopwatch();
        private bool stopwatchRunning = false;
        private float maxDelay = 0;
        private float minDelay = 10;
        private float totalDelay = 0;
        private List<float> listDelays = new List<float>();
        private long timesMeasured = 0;
        private int tooLongDelay = 0;

        #endregion

        # region properties
        public GraphicsDevice GraphicsDevice
        {
            get
            {
                return display;
            }
        }

        public SpriteBatch SpriteBatch
        {
            get
            {
                return sprite;
            }
        }

        public Control WindowCont
        {
            get
            {
                return windowControl;
            }
        }
        #endregion

        public XNADirectXPresentation(Dictionary<string, ISymbols> symbols, Control window, int width, int height, bool windowed, bool P_testMode)
            : base(symbols)
        {
            windowControl = window;
            presentParams = new PresentationParameters();
            presentParams.BackBufferWidth = 0;
            presentParams.BackBufferHeight = 0;
            presentParams.BackBufferFormat = SurfaceFormat.Bgr565;
            presentParams.IsFullScreen = !windowed;
            presentParams.DeviceWindowHandle = window.Handle;
            testMode = P_testMode;
            //param.PresentationInterval = PresentInterval.Immediate;
            try
            {   
                display = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, presentParams);
            }
            catch (DeviceLostException ex)
            {
                // thrown when anything that takes over an
                // exclusive ownership of the screen (e.g. fullscreen mode)
                Console.Out.WriteLine("Exception caught: " + ex.ToString());
                Console.Out.WriteLine("Solving the exception by sleeping for a second");
                System.Threading.Thread.Sleep(1000);
                display = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, presentParams);
            }
            long freq = Stopwatch.Frequency;

            sprite = new SpriteBatch(display);
        }

        #region IGraphicDeviceExtention
        public void Release(bool disposing)
        {
            // Decrement the "how many controls sharing the device" reference count.
            if (Interlocked.Decrement(ref referenceCount) == 0)
            {
                // If this is the last control to finish using the
                // device, we should dispose the singleton instance.
                if (disposing)
                {
                    if (DeviceDisposing != null)
                        DeviceDisposing(this, EventArgs.Empty);

                    display.Dispose();
                }

                display = null;
            }
        }

        public void ResetDevice(int width, int height)
        {
            if (DeviceResetting != null)
                DeviceResetting(this, EventArgs.Empty);

            presentParams.BackBufferWidth = Math.Max(presentParams.BackBufferWidth, width);
            presentParams.BackBufferHeight = Math.Max(presentParams.BackBufferHeight, height);

            display.Reset(presentParams);

            if (DeviceReset != null)
                DeviceReset(this, EventArgs.Empty);
        }
        
        // IGraphicsDeviceService events.
        public event EventHandler<EventArgs> DeviceCreated;
        public event EventHandler<EventArgs> DeviceDisposing;
        public event EventHandler<EventArgs> DeviceReset;
        public event EventHandler<EventArgs> DeviceResetting;
        #endregion

        public void draw()
        {
            Monitor.Enter(monitor);
            try
            {
                display.Clear(ClearOptions.Target, Microsoft.Xna.Framework.Color.Black, 1.0f, 0);
                sprite.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
                RunnableComponent[] layers = new RunnableComponent[this.layers.Count()];
                this.layers.CopyTo(layers, 0, layers.Count());
                foreach (RunnableComponent l in layers)
                {
                    l.draw();
                }
                sprite.End();
                display.Present();
            }
            catch (NullReferenceException)
            {
                return;
            }
            finally
            {
                Monitor.Exit(monitor);
            }
        }

        #region soundRegion
        public override void initSound()
        {
            
            engine = new AudioEngine("Content\\SoundContainer.xgs");
            soundBank = new SoundBank(engine, "Content\\SoundBank.xsb");
            waveBank = new WaveBank(engine, "Content\\WaveBank.xwb");
        }

        public override void playSound(String Ps_soundName)
        {
            // eventhough this initialization should be prepared from
            // initSound() method, this is necessary to run the sound
            engine = new AudioEngine("Content\\SoundContainer.xgs");
            soundBank = new SoundBank(engine, "Content\\SoundBank.xsb");
            waveBank = new WaveBank(engine, "Content\\WaveBank.xwb");
            
            soundCue = soundBank.GetCue(Ps_soundName);
            soundCue.Play();

            // wait till it ends
            while (soundCue.IsPlaying)
            {
               Thread.Sleep(1);
            }
        }
        #endregion

        #region delayAnalysisRegion
        // Summary:
        //     Method used for starting stopped or stopping running stopwatch. 
        //     Used for analytic purposes of if a particaular parameter of Presentation was chosen.
        public void startStopClock()
        {
            if (testMode)
            {
                if (!stopwatchRunning)
                {
                    stopwatchRunning = true;
                    stopwatch.Reset();
                    stopwatch.Start();
                }
                else
                {
                    stopwatch.Stop();
                    stopwatchRunning = false;
                    resolveStopwatch();
                }
            }
        }

        private void resolveStopwatch()
        {
            long measuredMs = stopwatch.ElapsedMilliseconds;
            long measuredTicks = stopwatch.ElapsedTicks;

            Console.Out.Write("Stopping clock - ms: " + measuredMs);
            Console.Out.Write(" , recounted ms:" + ticksToMilis(measuredTicks));

            // if delay is 5+ ms
            if (measuredMs > 4)
            {
                tooLongDelay++;
                Console.Out.Write("- !!! too long delay.");
            }
            Console.Out.WriteLine();

            updateStatistics(measuredTicks);
        }

        private float ticksToMilis(long ticks)
        {
            //recoutning ticks to milis
            float seconds = (ticks / (float)Stopwatch.Frequency);
            float milliseconds = seconds * 1000;

            return milliseconds;
        }

        private void updateStatistics(long P_ticks)
        {
            float milliseconds = ticksToMilis(P_ticks);
            listDelays.Add(milliseconds);

            timesMeasured++;
            if ((timesMeasured % 1000) == 0)
            {
                foreach (float delay in listDelays)
                {
                    Console.Out.WriteLine(delay + " ");
                }

                Process[] processes = System.Diagnostics.Process.GetProcesses();
                ProcessStartInfo info = processes[0].StartInfo;
                int i = processes[0].SessionId;
            }

            if (milliseconds > maxDelay)
            {
                maxDelay = milliseconds;
            }
            if (milliseconds < minDelay)
            {
                minDelay = milliseconds;
            }
            totalDelay = totalDelay + milliseconds;
            double avg = totalDelay / timesMeasured;

            Console.Out.WriteLine("Measurement nr." + timesMeasured);
            Console.Out.WriteLine("Max delay is " + maxDelay);
            Console.Out.WriteLine("Min delay is " + minDelay);
            Console.Out.WriteLine("Avg delay is " + avg);
            Console.Out.WriteLine("Latency was too high " + tooLongDelay + " times.");
        }
        #endregion

        public void setPresentationHighPrio(bool high)
        {
            System.Diagnostics.Process thisProc = Process.GetCurrentProcess();

            if (high)
            {
                thisProc.PriorityClass = ProcessPriorityClass.RealTime;
            }
            else
            {
                thisProc.PriorityClass = ProcessPriorityClass.Normal;
            }

            Console.WriteLine("PreSti set to priority class: {0}", thisProc.PriorityClass);
        }

        public override bool supportsGraphics()
        {
            return true;
        }

        public override bool supportsAudio()
        {
            return true;
        }
    }
}
